package com.siyoumi.app.sys.service.wxapi;

import com.siyoumi.app.sys.vo.BillOrderData;
import com.siyoumi.component.LogPipeLine;
import com.siyoumi.component.XApp;
import com.siyoumi.component.api.XWxApiPay;
import com.siyoumi.config.SysConfig;
import com.siyoumi.entity.SysAccsuperConfig;
import com.siyoumi.exception.EnumSys;
import com.siyoumi.util.*;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.LinkedMap;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

//微信支付
@Slf4j
public class WxApiPay
        extends ApiBase {
    static public WxApiPay getIns() {
        return new WxApiPay();
    }

    static public WxApiPay getIns(SysAccsuperConfig entityConfig) {
        WxApiPay app = getIns();
        app.setConfig(entityConfig);
        return app;
    }

    @Override
    protected String getApiUrl(String urlFix) {
        return XStr.concat("https://api.mch.weixin.qq.com/", urlFix);
    }

    //下单
    public XReturn order(String orderId, long fee, String orderDesc, String openid) {
        String url = getApiUrl("v3/pay/transactions/jsapi");
        if (getConfig().isServer()) {
            url = getApiUrl("v3/pay/partner/transactions/jsapi");
        }
        //String notifyUrl = SysConfig.getIns().getAppRoot() + "notify/wx_pay";
        String notifyUrl = getConfig().getNotifyUrl();

        HashMap<String, Object> postData = new HashMap<>();
        //用户
        HashMap<String, Object> payer = new HashMap<>();
        if (getConfig().isServer()) {
            log.info("服务模式");
            postData.put("sp_mchid", getConfig().getMchId());
            postData.put("sp_appid", getConfig().getPayAppId());

            postData.put("sub_mchid", getConfig().getMchIdSub());
            if (XStr.hasAnyText(getConfig().getAppIdSub())) {
                postData.put("sub_appid", getConfig().getAppIdSub());
            }
            payer.put("sub_openid", openid);
        } else {
            log.info("普通模式");
            postData.put("mchid", getConfig().getMchId());
            postData.put("appid", getConfig().getPayAppId());
            payer.put("openid", openid);
        }
        postData.put("payer", payer); //openid

        postData.put("out_trade_no", orderId);
        postData.put("description", orderDesc);
        postData.put("notify_url", notifyUrl);
        //postData.put("time_expire", null); //格式：2015-05-20T13:29:35+08:00
        //订单金额
        HashMap<String, Object> amount = new HashMap<>();
        amount.put("total", fee);
        amount.put("currency", "CNY");
        postData.put("amount", amount);

        String postStr = XJson.toJSONString(postData);
        log.debug("post_str: {}", postStr);
        return api(url, XHttpClient.METHOD_POST, postData);
    }

    @SneakyThrows
    public XReturn orderInfo(String orderId, String orderIdEx) {
        String url = "";
        if (XStr.hasAnyText(orderId)) { //第三方订单号
            url = getApiUrl("v3/pay/transactions/out-trade-no/") + orderId;
        }
        if (XStr.hasAnyText(orderIdEx)) { //微信订单号
            url = getApiUrl("v3/pay/transactions/id/") + orderIdEx;
        }
        url = XStr.concat(url, "?mchid=", getConfig().getMchId());

        return api(url);
    }

    @SneakyThrows
    public XReturn certificates() {
        String url = getApiUrl("v3/certificates");
        return api(url);
    }

    /**
     * 获取支付拉起参数
     *
     * @param prepayId 下单接口返回
     */
    @SneakyThrows
    public Map<String, String> getJsApiData(String prepayId) {
        LinkedMap<String, String> data = new LinkedMap<>();

        long timeStamp = XDate.toS();
        String nonceStr = XApp.getStrID();
        String prepay = XStr.concat("prepay_id=", prepayId);

        //顺序不能变，会影响签名
        data.put("appId", getConfig().getAppId());
        data.put("timeStamp", String.valueOf(timeStamp));
        data.put("nonceStr", nonceStr);
        data.put("package", prepay);

        log.debug("开始签名");
        //获取证书和证书序列号
        PrivateKey privateKey = XWxApiPay.loadPrivateKey(getConfig().getMchId());
        X509Certificate certificate = XWxApiPay.loadCertificate(getConfig().getMchId());
        String serialNumber = XWxApiPay.getSerialNumber(certificate);

        PrivateKeySigner signer = new PrivateKeySigner(serialNumber, privateKey);

        StringBuffer sb = new StringBuffer();
        data.forEach((k, v) ->
        {
            sb.append(v);
            sb.append("\n");
        });
        String string1 = sb.toString();

        Signer.SignatureResult signRes = signer.sign(string1.getBytes(StandardCharsets.UTF_8));
        String sign = signRes.getSign();
        log.debug("string1: {}", string1);
        log.debug("sign: {}", sign);

        data.put("signType", "RSA");
        data.put("paySign", sign);

        return data;
    }


    /**
     * 微信订单ID
     */
    public String orderWxId(XReturn r) {
        return r.getData("transaction_id", "");
    }

    /**
     * 订单是否已支付
     */
    public Boolean isPayOk(XReturn r) {
        String orderIdEx = orderWxId(r);
        if (XStr.hasAnyText(orderIdEx)) {
            return true;
        }

        return false;
    }


    /**
     * 订单申请退款
     *
     * @param orderId   支付订单号
     * @param refundId  退款订单号
     * @param orderFee  支付金额（分）
     * @param refundfee 退款金额（分）
     */
    public XReturn orderRefund(String orderId, String refundId, long orderFee, long refundfee) {
        String url = getApiUrl("v3/refund/domestic/refunds");

        HashMap<String, Object> postData = new HashMap<>();
        postData.put("out_trade_no", orderId);
        postData.put("out_refund_no", refundId);

        HashMap<String, Object> amount = new HashMap<>();
        amount.put("refund", refundfee);
        amount.put("total", orderFee);
        amount.put("currency", "CNY");
        postData.put("amount", amount);

        return api(url, XHttpClient.METHOD_POST, postData);
    }

    /**
     * 查询退款订单
     *
     * @param refundId
     */
    public XReturn orderRefundInfo(String refundId) {
        String url = getApiUrl("v3/refund/domestic/refunds/") + refundId;

        return api(url);
    }

    /**
     * 商家提现
     * <p>
     * 转账场景报备背景信息 查看链接
     * https://pay.weixin.qq.com/doc/v3/merchant/4012711988
     * 佣金报酬：岗位类型、报酬说明
     */
    public XReturn billOrder(BillOrderData data) {
        String url = getApiUrl("v3/fund-app/mch-transfer/transfer-bills");
        HashMap<String, String> mapHeader = new HashMap<>();

        HashMap<String, Object> postData = new HashMap<>();
        postData.put("appid", getConfig().getPayAppId());
        postData.put("out_bill_no", data.getOut_bill_no());
        postData.put("transfer_scene_id", data.getTransfer_scene_id());
        postData.put("transfer_remark", data.getTransfer_remark());
        postData.put("transfer_amount", data.getFee());
        postData.put("openid", data.getOpenid());
        if (XStr.hasAnyText(data.getUser_name()) && data.getFee() >= 30) { //3毛以下会报错
            postData.put("user_name", encryptTxt(data.getUser_name()));
            mapHeader.put("Wechatpay-Serial", XWxApiPay.getSerialNumber(XWxApiPay.loadCertificate(getConfig().getMchId(), "wechatpay")));
        }
        postData.put("transfer_scene_report_infos", data.getReportInfos("", ""));
        log.debug("post_json: {}", XJson.toJSONString(postData));
        return api(url, XHttpClient.METHOD_POST, postData, mapHeader);
    }

    /**
     * 商家提现订单查询
     * <p>
     * ACCEPTED: 转账已受理
     * PROCESSING: 转账锁定资金中。如果一直停留在该状态，建议检查账户余额是否足够，如余额不足，可充值后再原单重试。
     * WAIT_USER_CONFIRM: 待收款用户确认，可拉起微信收款确认页面进行收款确认
     * TRANSFERING: 转账中，可拉起微信收款确认页面再次重试确认收款
     * SUCCESS: 转账成功
     * FAIL: 转账失败
     * CANCELING: 商户撤销请求受理成功，该笔转账正在撤销中
     * CANCELLED: 转账撤销完成
     */
    public XReturn billOrderInfo(String orderId) {
        String url = getApiUrl("v3/fund-app/mch-transfer/transfer-bills/out-bill-no/" + orderId);
        return api(url, XHttpClient.METHOD_GET, null);
    }

    /**
     * 重写调用方法，增加微信签名sdk
     */
    @Override
    @SneakyThrows
    protected XReturn api(String url, String method, Map<String, Object> postData, Map<String, String> headers) {
        LogPipeLine.ofSys().setLogMsgFormat("[{0}]{1}", method, url);

        XReturn r = EnumSys.WXAPI_ERROR.getR();

        WechatPayHttpClientBuilder httpClientBuilder = getHttpClientBuilder();
        CloseableHttpClient httpClient = httpClientBuilder.build();
        URIBuilder uriBuilder = new URIBuilder(url);
        try {
            CloseableHttpResponse response;
            if (XHttpClient.METHOD_GET.equals(method)) {
                HttpGet httpGet = new HttpGet(uriBuilder.build());
                httpGet.addHeader("Accept", "application/json");
                response = httpClient.execute(httpGet);
            } else {
                //post请求
                HttpPost httpPost = new HttpPost(uriBuilder.build());
                //header
                httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");
                httpPost.addHeader("Accept", "application/json");
                if (headers != null) {
                    for (Map.Entry<String, String> entry : headers.entrySet()) {
                        httpPost.addHeader(entry.getKey(), entry.getValue());
                    }
                }

                String postStr = XJson.toJSONString(postData);
                httpPost.setEntity(new StringEntity(postStr, "UTF-8"));
                response = httpClient.execute(httpPost);
            }

            String returnStr = EntityUtils.toString(response.getEntity());
            r = XReturn.parse(returnStr);
            String code = r.getData("code", ""); //有code表示接口返回失败
            if (XStr.hasAnyText(code)) {
                r.setErrCode(EnumSys.WXAPI_ERROR.getErrcode());
                r.setErrMsg(r.getData("message", ""));
            }
        } catch (Exception ex) {
            r.setErrMsg(ex.getMessage());
        }
        LogPipeLine.ofSys().setLogMsg(r.toString());

        return r;
    }

    @SneakyThrows
    protected WechatPayHttpClientBuilder getHttpClientBuilder() {
        PrivateKey pk = XWxApiPay.loadPrivateKey(getConfig().getMchId());

        X509Certificate certificate = XWxApiPay.loadCertificate(getConfig().getMchId());
        String serialNumber = XWxApiPay.getSerialNumber(certificate);
        //merchantId:商户号,serialNo:商户证书序列号
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(getConfig().getMchId()
                , new WechatPay2Credentials(getConfig().getMchId()
                        , new PrivateKeySigner(serialNumber, pk))
                , getConfig().getMchKey().getBytes(StandardCharsets.UTF_8)
        );
        // 从证书管理器中获取verifier
        //版本>=0.4.0可使用 CertificatesManager.getVerifier(mchId) 得到的验签器替代默认的验签器。
        // 它会定时下载和更新商户对应的微信支付平台证书 （默认下载间隔为UPDATE_INTERVAL_MINUTE）。
        Verifier verifier = certificatesManager.getVerifier(getConfig().getMchId());

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(getConfig().getMchId(), serialNumber, pk)
                .withValidator(new WechatPay2Validator(verifier));

        return builder;
    }

    /**
     * rsa加密文本
     *
     * @param txt
     */
    @SneakyThrows
    public String encryptTxt(String txt) {
        X509Certificate certificate = XWxApiPay.loadCertificate(getConfig().getMchId(), "wechatpay");

        return rsaEncryptOAEP(txt, certificate);
    }


    @SneakyThrows
    protected static String rsaEncryptOAEP(String message, X509Certificate certificate) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
            byte[] data = message.getBytes(StandardCharsets.UTF_8);
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.getEncoder().encodeToString(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }
}
